package logic.strategy;

import java.io.Serializable;
import java.util.Map;
import java.util.Random;
import java.util.HashMap;
import java.util.Set;
import java.util.Stack;
import java.util.Map.Entry;
import java.util.ArrayList;

import logic.Continent;
import logic.GameMap;
import logic.Player;
import logic.Country;
import logic.moves.AttackMove;
import logic.moves.AddSoldierMove;
import logic.moves.GameMove;

/**
 * 
 * @author Paul
 *	Abstract class to support different implementations of Strategy for game missions
 *	The artificial intelligence in the game will use a strategy per given mission.
 *	Each strategy may inquire the game state at any moment and compute the next
 *	move on demand. Once this move is computed, it is returned to the caller,
 *	which will generally be some implementation of logical manager (coordinator of turns)
 *	and move will be set in motion.
 *	Game coordinator will ask artificial intelligence if has any more moves in store
 *	if answer is yes, then it will repeat the process.
 *
 */
public abstract class Strategy implements Serializable {

	private static final long serialVersionUID = 1L;
	protected boolean 	passTurn;
	protected Player	player;
	protected GameMap	map;
	protected Stack<AddSoldierMove> addSoldiers;
	protected Random 	rand;
	protected Player	victim;
	protected Continent cont;
	
	/**
	 * 
	 * @return next move to play, within context 
	 * of given mission and game state
	 * @return null if has no next move, hasNextMove == false
	 */
	public abstract GameMove getNextMove();
	/**
	 * 
	 * @return true if can continue making more moves,
	 * false otherwise
	 */
	public boolean hasNextMove()
	{
		return !passTurn;
	}
	
	public void init(GameMap map, Player player)
	{
		this.map = map;
		this.player = player;
		rand = new Random(System.currentTimeMillis());
	}
	protected AttackMove getAttackMove()
	{
		WeightedGraph graph = new WeightedGraph(map.countries.size());
		
		for (Country country1 : map.countries.values()) {
			graph.setLabel(map.getCountryIndex(country1), country1);
		}
			
		for (Country country1 : map.countries.values())	
			
			for (Country country2 : country1.getNeighbors().values()) {
				int countryWeight2 = 
					 country2.getSoldiers();
				
				// add incoming edge to country, if not mine
				if(country2.getOwner() != player)
					graph.addEdge(
							map.getCountryIndex(country1),
							map.getCountryIndex(country2),
							countryWeight2 );
			}
				
		int source=0; 
		int target=0;
		int minCost = Integer.MAX_VALUE; // first compare with highest value
		for (Country myCountry : player.getCountries().values())
		{
			if(!myCountry.canAttack())
				continue;
			for (Country targetCountry : this.getTargetCountries().values()) {
				
				int idx1 = map.getCountryIndex(myCountry);
				int idx2 = map.getCountryIndex(targetCountry);
								
				int cost = graph.getShortestPathWeight(idx1, idx2);
				if(cost == Integer.MAX_VALUE)
					continue;
				cost -= myCountry.getSoldiers();
				
/*				// TODO: only for debugging
				for (Object country : graph.getShortestPath(idx1, idx2)) {
					System.out.print((Country)country + " (" + ((Country)country).getSoldiers() + ") ->");
				}
				System.out.println("Total cost: " + cost);*/
				
				if (minCost > cost) {					
					minCost = cost;
					source = idx1;
					target = idx2;					
				}		
			}
		}
		if(minCost == Integer.MAX_VALUE)
			return null;
		Country bestSourceCountry = (Country)graph.getLabel(source);
		Country bestTargetCountry = (Country)graph.getShortestPath(source, target).get(1);
		
		AttackMove attackMove = new AttackMove(bestSourceCountry, bestTargetCountry, 1);
		
		return attackMove;
		
	}
	
	protected AddSoldierMove getAddSoldierMove() {
		// TODO
		return null;
	}
	/**
	 * @return Target Countries that strategy need to conquer
	 */
	public abstract HashMap<String, Country> getTargetCountries();
	public boolean declareVictory()
	{
		return player.isMissionAccomplished(); 
	}
	protected Country findAddSoldierTarget(int r, Map<Country, Range> rangeDivision) {
		Country rc = null;
		for(Entry<Country, Range> entry : rangeDivision.entrySet())
		{
			if(entry.getValue().inRange(r))
			{
				rc = entry.getKey();
				break;
			}
		}
		return rc;
	}
	/**
	 * 
	 * @return a Map of threat or importance levels of each players country
	 * This level is defined by the neighboring target countries and their
	 * strength (counted in soldiers)
	 * The more target countries with more soldiers, a greater threat 
	 * level will be assigned to the country.
	 * Every country has a minimum treat level of 1.
	 */
	protected Map<Country, Integer> mapCountryThreatLevel() {
		Map<Country, Integer> rs = new HashMap<Country, Integer>();
		Map<String, Country> targetCountries = getTargetCountries();
		for(Country country : player.getCountries().values())
		{
			int threat = 1; // very country starts with at least one threat level
			for(Country neighbor : country.getNeighbors().values())
			{
				if(targetCountries.containsKey(neighbor.getName()))
				{
					threat += neighbor.getSoldiers();
				}
			}
			rs.put(country, threat);
		}
		return rs;
	}
	/**
	 * 
	 * @param threatLevels
	 * @return sum of all threat levels on all of the players countries
	 */
	protected int sumThreatLevel(Map<Country, Integer> threatLevels) {
		int rs = 0;
		for(Integer threat : threatLevels.values())
		{
			rs += threat;
		}
		return rs;
	}
	/**
	 * 
	 * @param threatLevels
	 * @return a Division of ranges for every country according to its threat level
	 * this division is uniformly distributed among players' countries
	 * and their threat levels. The size of the threat level
	 * is the size of the segment (range) in the uniform distribution
	 */
	protected Map<Country, Range> divideUniformRange(Map<Country, Integer> threatLevels) {
		Map<Country, Range> rs = new HashMap<Country, Range>();
		if(threatLevels.isEmpty())
			return rs;
		int currBound = 0;
		int nextBound;
		Range range;
		
		for(Entry<Country, Integer> entry : threatLevels.entrySet())
		{
			nextBound = currBound + entry.getValue();
			range = new Range(currBound, nextBound);
			currBound = nextBound;
			rs.put(entry.getKey(), range);
		}
		return rs;
	}
	
	/**
	 * Fills the stack of possible AddSoldier moves
	 * with moves that are distributed with the 
	 */
	protected void fillAddSoldiersStack()
	{
		Map<Country, Integer>	threatLevels = mapCountryThreatLevel();
		int 					sum = sumThreatLevel(threatLevels);
		Map<Country, Range>		rangeDivision = divideUniformRange(threatLevels);
		
		for(int i = 0; i < player.getSoldiers(); i++)
		{
			int r = rand.nextInt(sum);
			Country addSoldierCountry = findAddSoldierTarget(r, rangeDivision);
			AddSoldierMove addSoldier = new AddSoldierMove(player, addSoldierCountry, 1);
			addSoldiers.push(addSoldier);
		}
	}
	
	protected class Range
	{
		private int low;	/*included*/
		private int high;	/*excluded*/
		//=== Constructor ===/
		public Range(int low, int high) 
		{
			this.low = low;
			this.high = high;
		}
		protected boolean inRange(int n)
		{
			return (n > low - 1) && (n < high);
		}
		
	}
}
